﻿using SdmxMl.Common;
using SdmxMl.Manager;
using SdmxMl.SmsImportCfg;
using SdmxMl.Structure;
using System;
using System.Data;
using System.IO;

namespace ChopPlugin
{
    /// <summary> CHOP hierarchy SDMX creator </summary>
    public class ChopCodes
    {
        #region Internal Members

        /// <summary>Excel reader </summary>
        private ExcelDataReader.ExcelDataReader spreadsheetConcept = null;
        /// <summary>Array of codelist for hierarchy construction. One for each level</summary>
        private CodeListType[] arrCodelistLevel;
        /// <summary>Hierarchical codelist for CHOP construction</summary>
        private HierarchicalCodelistType hclChop;
        /// <summary>Hierarchy of CHOP</summary>
        private HierarchyType hierarchy;
        /// <summary>Artefact container manager</summary>
        private SmManager sm;

        #endregion

        public static string CHOP_AGENCY_ID = "CH1_CHOP";
        public static string ALIAS_BASE = "";

        #region Constants

        /// <summary>
        /// Type of information in CHOP are modelised as annotations
        /// </summary>
        public const string ANNOT_TYPE_EXCLUSION = "EXCLUDE";
        public const string ANNOT_TYPE_LATERAL = "LATERAL";
        public const string ANNOT_TYPE_HINT = "HINT";
        public const string ANNOT_TYPE_INCLUDE = "INCLUDE";
        public const string ANNOT_TYPE_REMARK = "REMARK";
        public const string ANNOT_TYPE_NOTE = "NB";
        public const string ANNOT_TYPE_CODABLE = "CODABLE";

        public const string CHOP_ID = "CHOP";
        public const string HIERARCHICAL_CODELIST_ID = "HCL_CHOP";
        public const string HIERARCHY_ID = "H_CHOP";
        public const string CODELIST_BASE_ID = "CL_CHOP_LV";
        public const int CHOP_NB_LEVELS = 6;
        public const string ROW_TYPE_CODE = "T";

        const int indexNbChar = 1;
        const int indexCode = 2;
        const int indexCodeParent = 4;
        const int indexType = 6;
        const int indexReference = 7;
        const int indexLevel = 5;
        const int indexDE = 8;
        const int indexFR = 11;
        const int indexIT = 14;
        const int indexLateral = 9;

        #endregion

        static ChopCodes()
        {
        }

        #region Constructor

        /// <summary>
        /// 
        /// </summary>
        /// <param name="depth">depth in hierarchy, starting at zero</param>
        /// <returns>Alias to refered codelist for this depth</returns>
        private string GetAliasForDepth(int depth)
        {
            return SdmxMl.Multiplexor.Mux.IsNewSmsRegistry ? ChopCodes.ALIAS_BASE + depth.ToString() : (depth + 1).ToString();
        }

        /// <summary> C'tor </summary>
        /// <param name="path">Path to Excel file holding CHOP information to import as CHOP hierarchical codelists</param>
        public ChopCodes(string path)
        {
            sm = new SmManager();

            spreadsheetConcept = LoadSources(path);
            arrCodelistLevel = new CodeListType[CHOP_NB_LEVELS];

            // Creates maintenable artefact for CHOP import
            hclChop = new HierarchicalCodelistType();
            hclChop.AgencyId = CHOP_AGENCY_ID;
            hclChop.Id = HIERARCHICAL_CODELIST_ID;
            hclChop.Name.UpdateDefaultText(CHOP_ID + "Hierarchy");
            hclChop.HierarchyList.Clear();
            sm.PutArtefact(SdmxArtefactType.HierarchicalCodelists, hclChop);

            // for all expected levels create a codelist
            for (int i = 0; i < arrCodelistLevel.Length; i++)
            {
                 CodelistRefType clRef = new CodelistRefType();
                 clRef.AgencyId = CHOP_AGENCY_ID;

                // Alias must be generated differently in new SDMX SMS
                 clRef.Alias = GetAliasForDepth(i);
                arrCodelistLevel[i] = new CodeListType();
                clRef.Id = arrCodelistLevel[i].Id = CODELIST_BASE_ID + (i + 1).ToString();
                arrCodelistLevel[i].AgencyId = CHOP_AGENCY_ID;
                arrCodelistLevel[i].Name.UpdateDefaultText( CHOP_ID +" level " + (i + 1).ToString());
                 
                hclChop.CodelistRefList.Add(clRef);
                sm.PutArtefact(SdmxArtefactType.CodeLists, arrCodelistLevel[i]);
            }
            // Create hierarchy container
            hierarchy = new HierarchyType(hclChop.CodelistRefList);
            hierarchy.Id = HIERARCHY_ID;
            hierarchy.Name.UpdateDefaultText(CHOP_ID);
            hclChop.HierarchyList.Add(hierarchy);
        }
        #endregion

        #region Excel Reader 

        /// <summary> Obtains Excel Sheets as a set of DataTables </summary>
        /// <param name="propertyPath">Path to Excel 2003 document holding Chop codes</param>
        /// <returns>the container of dataTables</returns>
        private ExcelDataReader.ExcelDataReader LoadSources(string propertyPath)
        {
            ExcelDataReader.ExcelDataReader spreadsheet = null;
            if (propertyPath == null)
                throw new ApplicationException("Please provides path.");

            FileStream fs = null;
            try
            {
                fs = new FileStream(propertyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                spreadsheet = new ExcelDataReader.ExcelDataReader(fs);
            }
            finally
            {
                if (fs != null)
                    fs.Close();
            }

            return spreadsheet;
        }
        #endregion

        #region Codes and Hierarchy

        /// <summary> Add Hierarchy item (reference to code in hierarchy)</summary>
        /// <param name="c">the code object</param>
        /// <param name="level">the level in hierarchy</param>
        /// <param name="parentCode">the parent code id</param>
        /// <param name="clIndex">the alias reference. </param>
        private void SetHierarchyRef(CodeType c, int level, string parentCode, int clIndex)
        {
            if (level > CHOP_NB_LEVELS)
                throw new ApplicationException("Invalid level provided: " + (level+1).ToString());
            
            CodeRefTypeList crefList = hierarchy.CodeRefList;
            for (int i = 0; i < level; i++)
                crefList = crefList[crefList.Count - 1].CodeRefList;
            string alias = GetAliasForDepth(clIndex);
            CodeRefType cref = new CodeRefType(c.Id, alias);
            crefList.Add(cref);
        }

        /// <summary> Construct codelists content and hierarchy relations </summary>
        /// <returns>Container of artefacts holding CHOP</returns>
        public SmManager ConstructCodes()
        {
            // Assume codes are located in first Excel sheet
            DataTable dt = spreadsheetConcept.WorkbookData.Tables[0];
            WorkSheetHelper wh = new WorkSheetHelper(dt);

            string previousId = string.Empty;
            string previousParent = string.Empty;
            CodeType c = null;

            // Skip two header rows and scan table
            for (int row = 3; row <= dt.Rows.Count; row++)
            {
                try
                {
                    string code = wh.GetCellContent(row, indexCode);
                    if (code.Length == 0)
                        break;
                    int nbChar = int.Parse(wh.GetCellContent(row, indexNbChar));

                    string level = wh.GetCellContent(row, indexLevel);
                    string type = wh.GetCellContent(row, indexType);
                    string txtDE = wh.GetCellContent(row, indexDE);
                    string txtFR = wh.GetCellContent(row, indexFR);
                    string txtIT = wh.GetCellContent(row, indexIT);
                    string lateral = wh.GetCellContent(row, indexLateral);

                    // To store a permutation information, we use a "trik" by giving an urn ending with langs concerned by permutation
                    Permutation permut = new Permutation(wh, row);

                    if (code != previousId)
                    {
                        previousId = code;
                        string parentCode = wh.GetCellContent(row, indexCodeParent);
                        parentCode = (nbChar > 2) ? parentCode.Substring(1).Replace(".", "") : parentCode;
                        c = new CodeType();
                        c.Id = (nbChar > 1) ? code.Substring(1).Replace(".", "") : code;
                        if (permut.HasMark)
                            c.Uri = permut.PermutationUri;

                        // the codelists are filled according to code length
                        int index = nbChar - 1; 
                        arrCodelistLevel[index].CodeList.Add(c);
                        SetHierarchyRef(c, int.Parse(level) - 1, parentCode, index);

                        // Needs a lateral annotation ?
                        if (lateral.Length > 0)
                        {
                            Annotation aLat = new Annotation(ANNOT_TYPE_LATERAL);
                            c.AnnotationList.Add(aLat);
                        }
                    }

                    // Text filling in Code name or targeted annotation according to row type
                    if (type == ROW_TYPE_CODE)
                    {
                        c.Name.UpdateForLanguage(LangSupport.GERMAN_ID, txtDE);
                        c.Name.UpdateForLanguage(LangSupport.FRENCH_ID, txtFR);
                        c.Name.UpdateForLanguage(LangSupport.ITALIAN_ID, txtIT);
                    }
                    else // as annotation
                    {
                        Annotation a = CreateAnnotationFromType(type);
                        a.Text.UpdateForLanguage(LangSupport.GERMAN_ID, txtDE);
                        a.Text.UpdateForLanguage(LangSupport.FRENCH_ID, txtFR);
                        a.Text.UpdateForLanguage(LangSupport.ITALIAN_ID, txtIT);
                        c.AnnotationList.Add(a);

                        if (permut.HasMark)
                            a.Url = new Uri(permut.PermutationUri);
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }


            //XmlDocument xDoc = sm.GetAsDocument();

            //xDoc.Save("C:\\DOC\\CHOP\\ChopMl.xml");

            return sm;
        }
        #endregion

        #region Annotation type

        public static Annotation CreateAnnotationFromType(string type)
        {
            Annotation a = new Annotation();
            switch (type)
            {
                case "X":
                    a.Type = ANNOT_TYPE_EXCLUSION;
                    break;
                case "B":
                    a.Type = ANNOT_TYPE_REMARK;
                    break;
                case "S":
                    a.Type = ANNOT_TYPE_HINT;
                    break;
                case "N":
                    a.Type = ANNOT_TYPE_NOTE;
                    break;
                case "I":
                    a.Type = ANNOT_TYPE_INCLUDE;
                    break;
                default:
                    throw new ApplicationException("Unsupported type for annotation: " + type);
            }
            return a;
        }
        
        #endregion
    }

}
